/**
 * User: alesj
 * Date: 2005.1.20
 * Time: 14:47:54
 * 
 * (C) Genera Lynx d.o.o.
 */

package com.generalynx.ecos.beans.orm;

import com.generalynx.ecos.beans.DependencyInjectionAspectSupport;
import org.hibernate.CallbackException;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
import org.hibernate.EntityMode;
import org.springframework.beans.factory.FactoryBean;

import java.io.Serializable;

public class DependencyInjectionInterceptorFactoryBean extends DependencyInjectionAspectSupport
        implements FactoryBean {

    private SessionFactory sessionFactory;

    private String sessionFactoryName;

    private Interceptor nextInterceptor;


    /**
     * As Hibernate doesn't support chaining of interceptors natively,
     * we add the ability for chaining via a delegate.
     */
    public void setNextInterceptor(Interceptor nextInterceptor) {
        this.nextInterceptor = nextInterceptor;
    }

    /**
     * Reutn the next Interceptor in the chain, or null if this is
     * the only interceptor.
     */
    public Interceptor getNextInterceptor() {
        return nextInterceptor;
    }

    /**
     * We need the Hibernate SessionFactory to work out identifier property name
     * to set PK on object.
     *
     * @param sessionFactory bean name of the Hibernate SessionFactory
     *                       this interceptor should configure persistent object instances for
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Alternative to the sessionFactory property.
     * Use this property to avoid circular dependencies between interceptor
     * and SessionFactory. The session factory will be looked up on validation.
     *
     * @param sessionFactoryName bean name of the Hibernate SessionFactory
     *                           this interceptor should configure persistent object instances for
     */
    public void setSessionFactoryName(String sessionFactoryName) {
        this.sessionFactoryName = sessionFactoryName;
    }

    protected void validateProperties() {
        if (this.sessionFactory == null && this.sessionFactoryName == null) {
            throw new IllegalArgumentException("Either sessionFactory or sessionFactoryName property must be set");
        }
    }


    public Object getObject() throws Exception {
        DependencyInjectionInterceptor dii = new DependencyInjectionInterceptor();
        dii.setNextInterceptor(nextInterceptor);
        return dii;
    }

    public Class getObjectType() {
        return Interceptor.class;
    }

    public boolean isSingleton() {
        return true;
    }


    /**
     * Lazily look up the session factory.
     *
     * @return the Hibernate SessionFactory this class should configure objects for
     */
    private SessionFactory getSessionFactory() {
        // We don't do this in validate, as that could still cause circular
        // dependency problems.
        if (this.sessionFactory == null) {
            this.sessionFactory = (SessionFactory)getBeanFactory().getBean(this.sessionFactoryName, SessionFactory.class);
        }
        return this.sessionFactory;
    }


    /**
     * Class of Hibernate Interceptor returned by this factory.
     */
    protected class DependencyInjectionInterceptor extends ChainedInterceptorSupport {

        public Object instantiate(String entityName, EntityMode entityMode, Serializable id) throws CallbackException {
            try {
                Class clazz = getSessionFactory().getClassMetadata(entityName).getMappedClass(entityMode);
                Object newEntity = createAndConfigure(clazz);
                setIdOnNewEntity(getSessionFactory(), entityName, id, newEntity, entityMode);
                return newEntity;
            } catch (NoAutowiringConfigurationForClassException ex) {
                // Delegate to next interceptor in the chain if we didn't find an instantiation rule.
                return super.instantiate(entityName, entityMode, id);
            }
        }
    }

}